package com.jprocms.module.cms.framework.tree;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jprocms.module.cms.framework.constant.CommonConstants;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author jprocms
 */
public class TreeServiceImpl<M extends BaseMapper<T>, T extends TreeDo> extends ServiceImpl<M, T> implements TreeService<T> {

    @Override
    public String generatePath(Serializable parentId) {
        if (parentId == null) {
            return CommonConstants.PATH_SPLIT;
        }
        T entityParent = getById(parentId);
        if (entityParent == null) {
            return CommonConstants.PATH_SPLIT;
        }
        TreeDo treeEntity = (TreeDo) entityParent;
        String parentPath = treeEntity.getNodePath();
        return genarateChildPath(parentPath, parentId);
    }

    private String genarateChildPath(String parentPath, Serializable parentId) {
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isBlank(parentPath)) {
            sb.append(CommonConstants.PATH_SPLIT);
        } else {
            sb.append(parentPath);
        }
        sb.append(parentId).append(CommonConstants.PATH_SPLIT);
        return sb.toString();
    }

    /**
     * 查询父节点
     *
     * @param id
     * @param containSelf 是否包含自己
     * @return
     */
    @Override
    public List<T> findParentNodesById(Serializable id, boolean containSelf) {
        T entity = getById(id);
        if (entity == null) {
            return new ArrayList<>();
        }
        String path = entity.getNodePath();
        List<Long> ids = getNodeIdsByPath(path);
        List<T> nodes = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(ids)) {
            nodes = getBaseMapper().selectBatchIds(ids);
        }
        if (containSelf) {
            nodes.add(entity);
        }
        return nodes;
    }


    private List<Long> getNodeIdsByPath(String path) {
        if (StringUtils.isBlank(path)) {
            return new ArrayList<>();
        }
        String[] strs = path.split(CommonConstants.PATH_SPLIT);
        return Arrays.stream(strs).filter(StringUtils::isNumeric).map(Long::valueOf).collect(Collectors.toList());
    }

    /**
     * 查询子节点
     *
     * @param id
     * @param containSelf 是否包含自己
     * @return
     */
    @Override
    public List<T> findChildrenById(Serializable id, boolean containSelf) {
        T entity = getById(id);
        if (entity == null) {
            return new ArrayList<>();
        }
        String path = genarateChildPath(entity.getNodePath(), id);
        LambdaQueryWrapper<T> queryWrapper = Wrappers.lambdaQuery(getEntityClass());
        queryWrapper.likeRight(TreeDo::getNodePath, path);
        List<T> list = getBaseMapper().selectList(queryWrapper);
        if (containSelf) {
            list.add(entity);
        }
        return list;
    }

    @Override
    public List<? extends Serializable> findChildrenIdById(Serializable id, boolean containSelf) {
        List<T> list = findChildrenById(id, containSelf);
        return com.jpro.framework.common.util.collection.CollectionUtils.convertList(list, T::getId);
    }

    @Override
    public List<T> listByPath(@NotNull LambdaQueryWrapper<T> wrapper, @NotNull String path) {
        wrapper.likeRight(T::getNodePath, path);
        return super.list(wrapper);
    }

    @Override
    public boolean save(T entity) {
        if (entity == null) {
            return false;
        }
        Serializable parentId = entity.getParentId();
        String path = null;
        if (parentId != null) {
            T parent = super.getById(parentId);
            if (parent != null) {
                path = parent.getNodePath();
            }
        }
        entity.setNodePath(TreePathUtil.generateChildPath(path, (Long) parentId));
        return super.save(entity);
    }

    @Override
    public boolean updateById(T entity) {
        T dbEntit = super.getById(entity.getId());
        Long newParentId = entity.getParentId();
        if (!com.jpro.framework.common.util.object.ObjectUtils.nullSafeEquals(newParentId, dbEntit.getParentId())) {
            updateTreeNodePath(entity, dbEntit.getNodePath());
        }
        return super.updateById(entity);
    }

    private void updateTreeNodePath(T entity, String oldNodePath) {
        String genPath = generatePath(entity.getParentId());
        entity.setNodePath(genPath);
        updateChildrenPath(oldNodePath, genPath, entity.getParentId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateChildrenPath(@NotNull String oldPath, @NotNull String newPath, @NotNull Long id) {
        String subPath = genarateChildPath(oldPath, id);
        LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
        wrapper.setEntityClass(getEntityClass());
        wrapper.select(T::getId, T::getNodePath);
        List<T> list = listByPath(wrapper, subPath);
        if (CollectionUtil.isNotEmpty(list)) {
            String childPath = genarateChildPath(newPath, id);
            int length = subPath.length();
            for (T t : list) {
                String path = t.getNodePath();
                String substring = path.substring(length);
                t.setNodePath(childPath + substring);
            }
            super.saveOrUpdateBatch(list);
        }
    }
}
